1 module query; 2 3 import std.algorithm; 4 import std.array; 5 import std.conv; 6 import std.string; 7 import language; 8 import node; 9 import tree_visitor; 10 import other; 11 import libc : TSQuery, TSQueryError, TSQueryMatch, TSQueryCursor, 12 TSQueryCapture, TSQueryPredicateStep; 13 14 /// A particular `Node` that has been captured with a particular name within a `Query`. 15 struct QueryCapture 16 { 17 /// The `Node` that was captured. 18 Node node; 19 /// the index 20 int index; 21 /// The name of the capture. 22 string name; 23 } 24 25 /// A match of a `Query` to a particular set of `Node`s. 26 struct QueryMatch 27 { 28 /// The id 29 uint id; 30 /// the pattern index 31 uint pattern_index; 32 /// the captures array 33 QueryCapture[] captures; 34 } 35 36 /// A sequence of `QueryMatch`es associated with a given `QueryCursor`. 37 struct QueryIterator 38 { 39 import libc : ts_query_cursor_new, ts_query_cursor_delete, ts_query_cursor_exec, 40 ts_query_cursor_next_match, ts_query_cursor_set_byte_range, 41 ts_query_cursor_set_point_range; 42 43 private Query* query; 44 private Node* node; 45 46 private TSQueryCursor* cursor; 47 48 @disable this(this); 49 50 /// Create a new `QueryIterator` for the given `Query` and `Node`. 51 this(Query* query, Node* node) 52 { 53 this.query = query; 54 this.node = node; 55 cursor = ts_query_cursor_new(); 56 ts_query_cursor_exec(cursor, query.tsquery, node.tsnode); 57 } 58 59 /// Create a new `QueryIterator` for the given `Query` and `Node` and given byte range. 60 this(Query* query, Node* node, uint min, uint max) 61 { 62 this(query, node); 63 set_byte_range(min, max); 64 } 65 66 /// Create a new `QueryIterator` for the given `Query` and `Node` and given point range. 67 this(Query* query, Node* node, Point min, Point max) 68 { 69 this(query, node); 70 set_point_range(min, max); 71 } 72 73 ~this() 74 { 75 ts_query_cursor_delete(cursor); 76 } 77 78 /** 79 * Adjusts the range in which the query will apply. 80 * `min` and `max` are byte offsets. 81 */ 82 void set_byte_range(uint min, uint max) 83 { 84 ts_query_cursor_set_byte_range(cursor, min, max); 85 } 86 87 /** 88 * Adjusts the range in which the query will apply. 89 * `min` and `max` are Point offsets. 90 */ 91 void set_point_range(Point min, Point max) 92 { 93 ts_query_cursor_set_point_range(cursor, min, max); 94 } 95 96 /// Returns the next `QueryMatch` in the sequence. 97 int opApply(scope int delegate(QueryMatch) dg) 98 { 99 TSQueryMatch match; 100 int result = 0; 101 while (ts_query_cursor_next_match(cursor, &match)) 102 { 103 auto captures = match.captures[0 .. match.capture_count].map!( 104 (capture) => QueryCapture(Node(capture.node), 105 capture.index, query.capture_name(capture))).array; 106 result = dg(QueryMatch(match.id, match.pattern_index, captures)); 107 if (result) 108 break; 109 } 110 return result; 111 } 112 } 113 114 /// An error that occurred when trying to create a `Query`. 115 class QueryException : Exception 116 { 117 /// the internal TSQueryError error 118 TSQueryError error; 119 /// Create a new `QueryException` with the given `TSQueryError`. 120 this(TSQueryError error) 121 { 122 super("QueryException: " ~ error.to!string); 123 this.error = error; 124 } 125 } 126 127 /// A query to retrieve information from the syntax tree. 128 struct Query 129 { 130 import libc : ts_query_new, ts_query_delete, ts_query_pattern_count, 131 ts_query_capture_count, ts_query_start_byte_for_pattern, 132 ts_query_predicates_for_pattern, 133 ts_query_step_is_definite, 134 ts_query_capture_name_for_id, 135 ts_query_disable_capture, ts_query_disable_pattern, 136 ts_query_string_count, ts_query_string_value_for_id; 137 138 /// The underlying `TSQuery` 139 TSQuery* tsquery; 140 /// The language of the query 141 private Language language; 142 143 @disable this(this); 144 145 /// Create a new query from a string containing one or more S-expression 146 /// patterns. 147 /// 148 /// The query is associated with a particular language, and can only be run 149 /// on syntax nodes parsed with that language. References to Queries can be 150 /// shared between multiple threads. 151 this(Language language, string queryString) 152 { 153 import std.conv; 154 155 this.language = language; 156 uint errOffset = 0; 157 TSQueryError errType; 158 this.tsquery = ts_query_new(language.tslanguage, queryString.toStringz, 159 queryString.length.to!uint, &errOffset, &errType); 160 161 if (errOffset != 0) 162 { 163 throw new QueryException(errType); 164 } 165 } 166 167 ~this() 168 { 169 ts_query_delete(tsquery); 170 } 171 172 /** 173 * Execute a query over an entire node. 174 * 175 * The caller may iterate over the result to receive a series of 176 * `QueryMatch` results. 177 */ 178 QueryIterator exec(Node node) 179 { 180 return QueryIterator(&this, &node); 181 } 182 183 /** 184 * Execute a query between given start and end byte offsets. 185 * 186 * The caller may iterate over the result to receive a series of 187 * `QueryMatch` results. 188 */ 189 QueryIterator exec(Node node, uint min, uint max) 190 { 191 return QueryIterator(&this, &node, min, max); 192 } 193 194 /** 195 * Execute a query between given start and end `Points`. 196 * 197 * The caller may iterate over the result to receive a series of 198 * `QueryMatch` results. 199 */ 200 QueryIterator exec(Node node, Point min, Point max) 201 { 202 return QueryIterator(&this, &node, min, max); 203 } 204 205 /** 206 * Get the number of patterns in the query. 207 */ 208 int pattern_count() @nogc nothrow 209 { 210 return ts_query_pattern_count(tsquery); 211 } 212 213 /** 214 * Get the number of captures in the query. 215 */ 216 int capture_count() @nogc nothrow 217 { 218 return ts_query_capture_count(tsquery); 219 } 220 221 /** 222 * Get the number of string literals in the query. 223 */ 224 int string_count() @nogc nothrow 225 { 226 return ts_query_string_count(tsquery); 227 } 228 229 /** 230 * Get the byte offset where the given pattern starts in the query's source. 231 * 232 * This can be useful when combining queries by concatenating their source 233 * code strings. 234 */ 235 int start_byte_for_pattern(uint patternId) @nogc nothrow 236 { 237 return ts_query_start_byte_for_pattern(tsquery, patternId); 238 } 239 240 /** 241 * Get all of the predicates for the given pattern in the query. 242 * 243 * The predicates are represented as a single array of steps. There are three 244 * types of steps in this array, which correspond to the three legal values for 245 * the `type` field: 246 * - `TSQueryPredicateStepTypeCapture` - Steps with this type represent names 247 * of captures. Their `value_id` can be used with the 248 * `ts_query_capture_name_for_id` function to obtain the name of the capture. 249 * - `TSQueryPredicateStepTypeString` - Steps with this type represent literal 250 * strings. Their `value_id` can be used with the 251 * `ts_query_string_value_for_id` function to obtain their string value. 252 * - `TSQueryPredicateStepTypeDone` - Steps with this type are *sentinels* 253 * that represent the end of an individual predicate. If a pattern has two 254 * predicates, then there will be two steps with this `type` in the array. 255 */ 256 const(TSQueryPredicateStep)[] predicates_for_pattern(uint patternId) @nogc nothrow 257 { 258 uint len; 259 auto ptr = ts_query_predicates_for_pattern(tsquery, patternId, &len); 260 return ptr[0 .. len]; 261 } 262 263 /** 264 * Check if a given step in a query is 'definite'. 265 * 266 * A query step is 'definite' if its parent pattern will be guaranteed to match 267 * successfully once it reaches the step. 268 */ 269 bool step_is_definite(uint byteOffset) @nogc nothrow 270 { 271 return ts_query_step_is_definite(tsquery, byteOffset); 272 } 273 274 /** 275 * Get the name of one of the query's captures. 276 * 277 * Each capture is associated with a numeric id based on the order that it 278 * appeared in the query's source. 279 */ 280 string capture_name_for_id(uint captureId) nothrow 281 { 282 uint len; 283 auto namePtr = ts_query_capture_name_for_id(tsquery, captureId, &len); 284 return namePtr[0 .. len].to!string; 285 } 286 287 /** 288 * Get the name of one of the query's captures, given a TSQueryCapture. 289 */ 290 string capture_name(TSQueryCapture capture) nothrow 291 { 292 return capture_name_for_id(capture.index); 293 } 294 /** 295 * Get the name of one of the query's string literals. 296 * 297 * Each string is associated with a numeric id based on the order that it 298 * appeared in the query's source. 299 */ 300 string query_string_value_for_id(uint id) nothrow 301 { 302 uint len; 303 auto namePtr = ts_query_string_value_for_id(tsquery, id, &len); 304 return namePtr[0 .. len].to!string; 305 } 306 307 /** 308 * Disable a certain capture within a query. 309 * 310 * This prevents the capture from being returned in matches, and also avoids 311 * any resource usage associated with recording the capture. Currently, there 312 * is no way to undo this. 313 */ 314 void disable_capture(string captureName) 315 { 316 ts_query_disable_capture(tsquery, captureName.toStringz, captureName.length.to!uint); 317 } 318 319 /** 320 * Disable a certain pattern within a query. 321 * 322 * This prevents the pattern from matching and removes most of the overhead 323 * associated with the pattern. Currently, there is no way to undo this. 324 */ 325 void disable_pattern(uint patternId) @nogc nothrow 326 { 327 ts_query_disable_pattern(tsquery, patternId); 328 } 329 }